{
  "nbformat": 4,
  "nbformat_minor": 5,
  "metadata": {},
  "cells": [
    {
      "metadata": {},
      "source": [
        "<td>\n",
        "   <a target=\"_blank\" href=\"https://labelbox.com\" ><img src=\"https://labelbox.com/blog/content/images/2021/02/logo-v4.svg\" width=256/></a>\n",
        "</td>"
      ],
      "cell_type": "markdown"
    },
    {
      "metadata": {},
      "source": [
        "<td>\n",
        "<a href=\"https://colab.research.google.com/github/Labelbox/labelbox-python/blob/master/examples/project_configuration/webhooks.ipynb\" target=\"_blank\"><img\n",
        "src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"></a>\n",
        "</td>\n",
        "\n",
        "<td>\n",
        "<a href=\"https://github.com/Labelbox/labelbox-python/blob/master/examples/project_configuration/webhooks.ipynb\" target=\"_blank\"><img\n",
        "src=\"https://img.shields.io/badge/GitHub-100000?logo=github&logoColor=white\" alt=\"GitHub\"></a>\n",
        "</td>"
      ],
      "cell_type": "markdown"
    },
    {
      "metadata": {},
      "source": [
        "# Webhook Configuration"
      ],
      "cell_type": "markdown"
    },
    {
      "metadata": {},
      "source": [
        "Webhooks are supported for the following events:\n",
        "* label_created\n",
        "* label_updated\n",
        "* label_deleted"
      ],
      "cell_type": "markdown"
    },
    {
      "metadata": {},
      "source": [
        "!pip install labelbox\n",
        "!pip install requests\n",
        "!pip install hmac\n",
        "!pip install hashlib\n",
        "!pip install flask\n",
        "!pip install Werkzeug"
      ],
      "cell_type": "code",
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {},
      "source": [
        "import labelbox as lb\n",
        "from flask import Flask, request\n",
        "import hmac\n",
        "import hashlib\n",
        "import threading\n",
        "from werkzeug.serving import run_simple\n",
        "import json\n",
        "import requests\n",
        "import os\n",
        "from getpass import getpass\n",
        "import socket"
      ],
      "cell_type": "code",
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {},
      "source": [
        "# If you don't want to give google access to drive you can skip this cell\n",
        "# and manually set `API_KEY` below.\n",
        "\n",
        "COLAB = \"google.colab\" in str(get_ipython())\n",
        "if COLAB:\n",
        "    !pip install colab-env -qU\n",
        "    from colab_env import envvar_handler\n",
        "\n",
        "    envvar_handler.envload()\n",
        "\n",
        "API_KEY = os.environ.get(\"LABELBOX_API_KEY\")\n",
        "if not os.environ.get(\"LABELBOX_API_KEY\"):\n",
        "    API_KEY = getpass(\"Please enter your labelbox api key\")\n",
        "    if COLAB:\n",
        "        envvar_handler.add_env(\"LABELBOX_API_KEY\", API_KEY)"
      ],
      "cell_type": "code",
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {},
      "source": [
        "# Set this to a project that you want to use for the webhook\n",
        "PROJECT_ID = \"\"\n",
        "# Only update this if you have an on-prem deployment\n",
        "ENDPOINT = \"https://api.labelbox.com/graphql\""
      ],
      "cell_type": "code",
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {},
      "source": [
        "client = lb.Client(api_key=API_KEY, endpoint=ENDPOINT)"
      ],
      "cell_type": "code",
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {},
      "source": [
        "# We are using port 3001 for this example.\n",
        "# Feel free to set to whatever port you want\n",
        "WH_PORT = 3001"
      ],
      "cell_type": "code",
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {},
      "source": [
        "### Configure NGROK (Optional)\n",
        "* If you do not have a public ip address then follow along\n",
        "\n",
        "1. Create an account:\n",
        "    https://dashboard.ngrok.com/get-started/setup\n",
        "2. Download ngrok and extract the zip file\n",
        "3. Add ngrok to your path\n",
        "4. Add the authtoken `ngrok authtoken <token>`"
      ],
      "cell_type": "markdown"
    },
    {
      "metadata": {},
      "source": [
        "if not COLAB:\n",
        "    os.system(f\"ngrok http {WH_PORT} &\")"
      ],
      "cell_type": "code",
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {},
      "source": [
        "### Configure server to receive requests"
      ],
      "cell_type": "markdown"
    },
    {
      "metadata": {},
      "source": [
        "# This can be any secret that matches your webhook config (we will set later)\n",
        "secret = b\"example_secret\""
      ],
      "cell_type": "code",
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {},
      "source": [
        "app = Flask(__name__)\n",
        "\n",
        "\n",
        "@app.route(\"/\")\n",
        "def hello_world():\n",
        "    return \"Hello, World!\"\n",
        "\n",
        "\n",
        "@app.route(\"/webhook-endpoint\", methods=[\"POST\"])\n",
        "def print_webhook_info():\n",
        "    payload = request.data\n",
        "    computed_signature = hmac.new(secret, msg=payload,\n",
        "                                  digestmod=hashlib.sha1).hexdigest()\n",
        "    if request.headers[\"X-Hub-Signature\"] != \"sha1=\" + computed_signature:\n",
        "        print(\n",
        "            \"Error: computed_signature does not match signature provided in the headers\"\n",
        "        )\n",
        "        return \"Error\", 500, 200\n",
        "\n",
        "    print(\"=========== New Webhook Delivery ============\")\n",
        "    print(\"Delivery ID: %s\" % request.headers[\"X-Labelbox-Id\"])\n",
        "    print(\"Event: %s\" % request.headers[\"X-Labelbox-Event\"])\n",
        "    print(\"Payload: %s\" %\n",
        "          json.dumps(json.loads(payload.decode(\"utf8\")), indent=4))\n",
        "    return \"Success\"\n",
        "\n",
        "\n",
        "thread = threading.Thread(target=lambda: run_simple(\"0.0.0.0\", WH_PORT, app))\n",
        "thread.start()"
      ],
      "cell_type": "code",
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {},
      "source": [
        "#### Test server"
      ],
      "cell_type": "markdown"
    },
    {
      "metadata": {},
      "source": [
        "print(requests.get(\"http://localhost:3001\").text)"
      ],
      "cell_type": "code",
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {},
      "source": [
        "### Create Webhook"
      ],
      "cell_type": "markdown"
    },
    {
      "metadata": {},
      "source": [
        "- Set ip address if your ip is publicly accessible.\n",
        "- Otherwise use the following to get ngrok public_url"
      ],
      "cell_type": "markdown"
    },
    {
      "metadata": {},
      "source": [
        "if not COLAB:\n",
        "    res = requests.get(\"http://localhost:4040/api/tunnels\")\n",
        "    assert (res.status_code == 200\n",
        "           ), f\"ngrok probably isn't running. {res.status_code}, {res.text}\"\n",
        "    tunnels = res.json()[\"tunnels\"]\n",
        "    tunnel = [\n",
        "        t for t in tunnels if t[\"config\"][\"addr\"].split(\":\")[-1] == str(WH_PORT)\n",
        "    ]\n",
        "    tunnel = tunnel[0]  # Should only be one..\n",
        "    public_url = tunnel[\"public_url\"]\n",
        "else:\n",
        "    public_url = f\"http://{socket.gethostbyname(socket.getfqdn(socket.gethostname()))}\"\n",
        "print(public_url)"
      ],
      "cell_type": "code",
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {},
      "source": [
        "# Set project to limit the scope to a single project\n",
        "project = client.get_project(PROJECT_ID)\n",
        "topics = {topic.value for topic in lb.Webhook.Topic}\n",
        "# For Global Webhooks (Global = per workspace) project = None\n",
        "webhook = lb.Webhook.create(client,\n",
        "                         topics=topics,\n",
        "                         url=public_url,\n",
        "                         secret=secret.decode(),\n",
        "                         project=project)"
      ],
      "cell_type": "code",
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {},
      "source": [
        "# Ok so we should be configured assuming everything is setup correctly.\n",
        "# Go to the following url and make a new label to see if it works\n",
        "print(f\"https://app.labelbox.com/projects/{PROJECT_ID}\")"
      ],
      "cell_type": "code",
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {},
      "source": [
        "### Update Webhook"
      ],
      "cell_type": "markdown"
    },
    {
      "metadata": {},
      "source": [
        "# url, topics, and status can all be updated\n",
        "updated_url = f\"{public_url}/webhook-endpoint\"\n",
        "print(updated_url)\n",
        "webhook.update(url=updated_url)\n",
        "# Go to the following url and try one last time.\n",
        "# Any supported action should work (create, delete, or update a label)\n",
        "print(f\"https://app.labelbox.com/projects/{PROJECT_ID}\")"
      ],
      "cell_type": "code",
      "outputs": [],
      "execution_count": null
    },
    {
      "metadata": {},
      "source": [
        "### List and delete all webhooks"
      ],
      "cell_type": "markdown"
    },
    {
      "metadata": {},
      "source": [
        "# DELETE:\n",
        "webhook.update(status=lb.Webhook.Status.INACTIVE.value)\n",
        "\n",
        "# FETCH ALL WEBHOOKS:\n",
        "org = client.get_organization()\n",
        "webhooks = org.webhooks()\n",
        "\n",
        "# Run this to clear all.\n",
        "# WARNING!!! THIS WILL DELETE ALL WEBHOOKS FOR YOUR ORG\n",
        "# ONLY RUN THIS IS YOU KNOW WHAT YOU ARE DOING.\n",
        "# for webhook in webhooks:\n",
        "#    print(webhook)\n",
        "#    webhook.update(status = lb.Webhook.Status.INACTIVE.value)"
      ],
      "cell_type": "code",
      "outputs": [],
      "execution_count": null
    }
  ]
}